// Package aggregate 是方法聚合结构，负责组合底层对外依赖接口来实现响应的功能
// AgentFactory 是关于agent的方法聚合，为上层domain提供基础调用
package aggregate

import (
	"errors"
	"fmt"
	"gluenet/internal/glue-srvp/domain/dependency"
	"gluenet/internal/glue-srvp/domain/entity"
	"gluenet/internal/glue-srvp/domain/po"
	"gluenet/internal/glue-srvp/domain/vo"
	"gluenet/internal/pkg/infra"
	"gluenet/internal/pkg/infra/prepare"
	"reflect"
	"time"
)

func init() {
	prepare.RegisterFactory(func() {
		intanceFactory := NewAgentFactory()
		prepare.InjectDependency(func() *AgentFactory {
			return intanceFactory
		})
	})
}

type AgentFactory struct {
	MetaRepo  dependency.MetaRepo
	GuidCli   dependency.GuidCLI
	MetricCli dependency.MetricCLI
}

func (r *AgentFactory) UnRegister(guid string) error {
	var obj entity.Agent
	if err := r.MetaRepo.FindOne(&obj, map[string]interface{}{
		"guid": guid,
	}); err != nil {
		return err
	}

	if _, err := r.MetaRepo.Del(&obj); err != nil {
		return err
	}
	return nil
}

func (r *AgentFactory) Register(req vo.AgentRegisterReq) (*entity.Agent, error) {
	rOrder := &entity.RegisterOrder{}
	rOrder.ObjectKind = infra.KindAgent
	rOrder.ObjectDetail = req

	guid, err := r.GuidCli.Register(rOrder)
	if err != nil {
		return nil, err
	}

	var group entity.Group
	err = r.MetaRepo.FindOne(&group, map[string]interface{}{
		"name": req.Organize,
	})
	if err != nil {
		return nil, err
	}

	var obj entity.Agent
	err = r.MetaRepo.FindOne(&obj, map[string]interface{}{
		"guid": guid,
	})
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	if errors.Is(err, infra.DataNotFind) {
		obj.GUID = guid
		obj.Platform = req.Platform
		obj.Role = req.Role
		obj.AreaID = req.AreaID
		obj.OrganizeId = group.Id

		var area entity.Area
		if err = r.MetaRepo.FindOne(&area, map[string]interface{}{
			"id": req.AreaID,
		}); err != nil {
			return nil, err
		}

		//obj.DetailRepoKey = filepath.ToSlash(filepath.Join(infra.DetailAgentRepoKeyFormat, obj.GUID))
		//obj.ConfigRepoKey = filepath.ToSlash(filepath.Join(infra.ConfigAgentRepoKeyFormat, obj.GUID))
		//obj.RunTimeRepoKey = filepath.ToSlash(filepath.Join(infra.RuntimeAgentRepoKeyFormat, obj.GUID))

		obj.DetailRepoValue = po.AgentDetail{
			ClusterName: req.ClusterName,
			Area: po.Area{
				AreaID:     area.AreaID,
				Country:    area.Country,
				Province:   area.Province,
				City:       area.City,
				County:     area.County,
				DetailInfo: area.DetailInfo,
			},
			ParentGuid: req.ParentGUID,
		}

		obj.RunTimeRepoValue = po.AgentRuntime{
			ClusterNodes: req.ClusterNodes,
		}

		if req.Config == nil {
			var dict = entity.Dict{}
			if err = r.MetaRepo.FindOne(&dict, map[string]interface{}{
				"module":   "agent",
				"dict_key": fmt.Sprintf("boot_config_%v_%v", req.Platform, req.Role),
			}); err != nil {
				return nil, err
			}
			obj.ConfigRepoValue.FromDB([]byte(dict.Value))
		} else {
			obj.ConfigRepoValue = *req.Config
		}

		NewObj, err := r.MetaRepo.Add(&obj)
		if err != nil {
			return nil, err
		}

		k := NewObj.(*entity.Agent)
		return k, nil
	} else {
		NewObj, err := r.MetaRepo.Upt(&obj)
		if err != nil {
			return nil, err
		}
		k := NewObj.(*entity.Agent)
		return k, nil
	}
}

func (r *AgentFactory) GetConfig(guid string) (interface{}, error) {
	var obj entity.Agent
	if err := r.MetaRepo.FindOne(&obj, map[string]interface{}{
		"guid": guid,
	}); err != nil {
		return nil, err
	}
	return obj.ConfigRepoValue, nil
}

func (r *AgentFactory) PutConfig(req vo.AgentPutConfigReq) error {
	var obj entity.Agent
	if err := r.MetaRepo.FindOne(&obj, map[string]interface{}{
		"guid": req.Guid,
	}); err != nil {
		return err
	}

	obj.ConfigRepoValue = req.ConfigInfo

	_, err := r.MetaRepo.Upt(obj)
	return err
}

func (r *AgentFactory) QueryAgentAll() ([]entity.Agent, error) {
	var objs []entity.Agent
	err := r.MetaRepo.FindAll(&objs)
	if err != nil {
		return nil, err
	}
	return objs, nil
}

func (r *AgentFactory) QueryAgent(query map[string]interface{}) ([]entity.Agent, error) {
	var objs []entity.Agent
	err := r.MetaRepo.FindBy(&objs, query)
	if err != nil {
		return nil, err
	}
	return objs, nil
}

func (r *AgentFactory) QueryArea(id string) (*entity.Area, error) {
	var obj entity.Area

	if err := r.MetaRepo.FindOne(&obj, map[string]interface{}{
		"id": id,
	}); err != nil {
		return nil, err
	}

	return &obj, nil
}

func (r *AgentFactory) ClusterRuntime(organize int64) (interface{}, error) {
	var objs []entity.Agent
	if err := r.MetaRepo.FindBy(&objs, map[string]interface{}{
		"organize_id": organize,
		"platform":    infra.PlatformKubernetes,
	}); err != nil {
		return nil, err
	}

	//_, err := r.MetricCli.MetricsNow(fmt.Sprintf(`{__name__="metrics",label=~""}`))
	//if err != nil {
	//	if !errors.Is(err, infra.DataNotFind) {
	//		return nil, err
	//	}
	//	log.Warnf("query agent runtime metric: data not find")
	//}

	//var res = make(map[string]vo.AgentRuntimeRes, len(objs))
	//for _, v := range objs {
	//	ars := vo.AgentRuntimeRes{
	//		GUID:             v.GUID,
	//		ClusterName:      v.DetailRepoValue.ClusterName,
	//		Platform:         v.Platform,
	//		ClusterNodes:     len(v.RunTimeRepoValue.ClusterNodes),
	//		AreaCountry:      v.DetailRepoValue.Area.Country,
	//		AreaProvince:     v.DetailRepoValue.Area.Province,
	//		AreaCity:         v.DetailRepoValue.Area.City,
	//		AreaCounty:       v.DetailRepoValue.Area.County,
	//		Cores:            1,
	//		MemSize:          1,
	//		CpuUsage:         0.1,
	//		MemUsage:         0.9,
	//		RunningTime:      time.Now().Unix() - v.Created.Unix(),
	//		ClusterNodesInfo: v.RunTimeRepoValue.ClusterNodes,
	//	}
	//	res[v.GUID] = ars
	//}

	var res = make([]vo.AgentRuntimeResV2, len(objs))
	for k, v := range objs {
		ars := vo.AgentRuntimeResV2{
			GUID:        v.GUID,
			ClusterName: v.DetailRepoValue.ClusterName,
			Platform:    v.Platform,
			Hosts:       len(v.RunTimeRepoValue.ClusterNodes),
			//AreaCountry:      v.DetailRepoValue.Area.Country,
			//AreaProvince:     v.DetailRepoValue.Area.Province,
			//AreaCity:         v.DetailRepoValue.Area.City,
			//AreaCounty:       v.DetailRepoValue.Area.County,
			Area:        v.DetailRepoValue.Area.City,
			Cores:       1,
			MemSize:     1,
			CpuUsage:    0.1,
			MemUsage:    0.9,
			RunningTime: time.Now().Unix() - v.Created.Unix(),
			//ClusterNodesInfo: v.RunTimeRepoValue.ClusterNodes,
		}
		res[k] = ars
	}

	var insRes = make([]interface{}, len(res)+1)
	var tags []interface{}
	for k, obj := range res {
		if k == 0 {
			e := reflect.TypeOf(obj)
			num := e.NumField()
			tags = make([]interface{}, num)
			for i := 0; i < num; i++ {
				tags[i] = e.Field(i).Tag.Get(`json`)
			}
			insRes[0] = tags
		}

		var vals = make([]interface{}, len(tags))
		e := reflect.ValueOf(obj)
		for fid := range tags {
			val := e.Field(fid).Interface()
			vals[fid] = val
		}
		insRes[k+1] = vals
	}
	return insRes, nil

	//return res, nil
}

func (r *AgentFactory) ClusterChildInfo(organize int64) (interface{}, error) {
	var objs []entity.Agent
	if err := r.MetaRepo.FindBy(&objs, map[string]interface{}{
		"organize_id": organize,
		"platform":    infra.PlatformKubernetes,
	}); err != nil {
		return nil, err
	}

	var res = make(map[string]*vo.AgentRuntimeChild, len(objs))
	for _, v := range objs {
		ags := vo.AgentRuntimeChild{}
		ags.ClusterName = v.DetailRepoValue.ClusterName
		ags.Guid = v.GUID
		ags.Nodes = make(map[string]*vo.AgentNodeInfo)
		for _, nd := range v.RunTimeRepoValue.ClusterNodes {
			node := vo.AgentNodeInfo{}
			node.NodeName = nd.NodeName
			node.CpuCores = nd.CpuCores
			node.MemSize = nd.MemSize
			ags.Nodes[nd.NodeName] = &node
		}
		res[v.GUID] = &ags
	}
	return res, nil
}

func (r *AgentFactory) UpdateNodes(auth infra.AuthInfo) error {
	var (
		err  error
		objs []entity.Agent
	)
	if err = r.MetaRepo.FindBy(&objs, map[string]interface{}{}); err != nil {
		return err
	}

	for _, obj := range objs {
		r.MetricCli.UpdateAgentNodesMetric(&obj)
		if _, err = r.MetaRepo.Upt(&obj); err != nil {
			return err
		}
	}
	return nil
}

func NewAgentFactory() *AgentFactory {
	factory := &AgentFactory{}
	prepare.FetchDependency(&factory.GuidCli)
	prepare.FetchDependency(&factory.MetaRepo)
	prepare.FetchDependency(&factory.MetricCli)
	return factory
}
